import asyncio
import random
from collections import Counter

from PySide2.QtCore import *
from shapely.geometry import Point
from pyproj import Proj
from TESS_PythonAPI.Tessng import *
from tess_local_simulator.KafkaMsgProducer import KProducer
from kafka import KafkaConsumer
import json
import time
from tess_local_simulator.websocketserver1 import WebSocketServer
from utils.lon_lat_util import haversine_distance
from net_file.config import KAKO_DATA_TOPIC, \
    KAFKA_HOST, TESSNG_DATA_TOPIC, KAKO_DATA_REMOVE, KAKO_DATA_S, KAKO_DATA_X, DEF_SPEED_LIMIT_CONFIG
from datetime import datetime
import threading


# 用户插件子类，代表用户自定义与仿真相关的实现逻辑，继承自PyCustomerSimulator
#     多重继承中的父类QObject，在此目的是要能够自定义信号signlRunInfo
type_dict = {1: 10, 10: 64, 3: 82}
car_ids = dict()
car_send_ids = dict()
# 车辆类型映射
car_real_type = {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 4, 8: 8, 9: 1, 10: 10, 11: 11, 15: 15, 16: 16, 17: 17,
                 18: 18}
# 车道号转化
car_lane_type = {0: 1, 1: 2, 2: 3}
car_real_type_default_value = 1

send_data = dict()


class CustomSimulator(QObject, PyCustomerSimulator):
    signalRunInfo = Signal(str)
    forStopSimu = Signal()
    forReStartSimu = Signal()

    def __init__(self, config_dict: dict):
        QObject.__init__(self)
        PyCustomerSimulator.__init__(self)
        # 当前正在仿真计算的路网名称
        self.mNetPath = None
        # 相同路网连续仿真次数
        self.mSimuCount = 0
        # 批次号
        self.batchNum = 0
        # 经纬度转化公式
        self.p = Proj(config_dict['proj'])
        # 缓存数据
        self.link_center_point_dict = {} # 所有路段中心点信息，K：路段id，V：该路段的中心点集合
        self.kako_data_list = {}  # 缓存所有的卡口车辆信息
        self.exists_list_licenseCode_vId = {}  # 当前仿真的车辆字典，K：车牌，V：tess车辆id
        self.exists_list_vId_licenseCode = {}  # 当前仿真的车辆字典，K：tess车辆id，V：车牌
        self.set_speed_list = {}  # 需要加速或减速的车辆集合
        self.test = {}
        # 超过此距离，进行速度的调整，否则不调整
        self.min_move_distance = 100
        # 接收数据线程
        receive_message_of_ai_data_thread = threading.Thread(target=self.receive_message_of_ai_data)
        # receive_message_of_ai_data_thread.start()
        # kafka生产者
        self.kp_tessng_data = KProducer(topic=TESSNG_DATA_TOPIC, bootstrap_servers=KAFKA_HOST)
        # websocket
        self.ws_server = WebSocketServer(8083)
        self.ws_server.start()
        self.loop = asyncio.get_event_loop()
        asyncio.set_event_loop(self.loop)

    # 接收AI数据
    def receive_message_of_ai_data(self):
        print("接收AI数据", 'kafka 消费被触发')
        consumer = KafkaConsumer(
            KAKO_DATA_TOPIC,
            bootstrap_servers=KAFKA_HOST,
            auto_offset_reset='latest',
            enable_auto_commit=False
        )
        while True:
            time.sleep(0.1 / 6)
            for message in consumer:
                if message:
                    try:
                        message_value = message.value.decode('utf-8')
                        vehicle_info = json.loads(message_value)
                        self.kako_data_list[vehicle_info['licenseCode']] = vehicle_info
                    except Exception as e:
                        print(str(e))


    def ref_beforeStart(self, ref_keepOn):
        iface = tessngIFace()
        # TESSNG 路网子接口
        net_iface = iface.netInterface()
        links = net_iface.links()
        print(f"路段总数：{len(links)}")
        for link in links:
            points_list = link.centerBreakPoints()
            link_id = link.id()
            print(f"路段id：{link.id()}，路段中心点集合个数：{len(points_list)}")
            point_list = []
            for point in points_list:
                lon, lat = self.p(point.x(), -point.y(), inverse=True)
                point_list.append((lon, lat))
            self.link_center_point_dict[link_id] = point_list
        print(self.link_center_point_dict)

        # 当前路网名
        tmp_net_path = iface.netInterface().netFilePath()
        if tmp_net_path != self.mNetPath:
            self.mNetPath = tmp_net_path
            self.mSimuCount = 1
        else:
            self.mSimuCount += 1
        # 可在此设置本次仿真参数
        ref_keepOn.value = True


    # 设置本类实现的过载方法被调用频次，即多少个计算周期调用一次。过多的不必要调用会影响运行效率
    def setStepsPerCall(self, vehi):
        # 设置当前车辆及其驾驶行为过载方法被TESSNG调用频次，即多少个计算周调用一次指定方法。如果对运行效率有极高要求，可以精确控制具体车辆或车辆类型及具体场景相关参数
        iface = tessngIFace()
        netface = iface.netInterface()
        netFileName = netface.netFilePath()
        # 允许对车辆重绘方法的调用
        vehi.setIsPermitForVehicleDraw(True)
        # 计算限制车道方法每10个计算周期被调用一次
        vehi.setSteps_calcLimitedLaneNumber(1)
        # 计算安全变道距离方法每10个计算周期被调用一次
        vehi.setSteps_calcChangeLaneSafeDist(1)
        # 设置计算 左自由/右自由 变道方法
        vehi.setSteps_reCalcToLeftFreely(1)
        vehi.setSteps_reCalcToRightFreely(1)
        # 设置计算  左强制变道方法、右强制变道方法  被调用频次，即 steps 个计算周期调用 1 次
        vehi.setSteps_reCalcToLeftLane(1)
        vehi.setSteps_reCalcToRightLane(1)
        # 重新计算车辆期望速度方法每一个计算周期被调用一次
        vehi.setSteps_reCalcdesirSpeed(1)
        # 重新设置车速方法每一个计算周期被调用一次
        vehi.setSteps_reSetSpeed(1)
        # 范例打开临时路段会会创建车辆方阵，需要进行一些仿真过程控制

        simuface = iface.simuInterface()
        # 仿真精度，即每秒计算次数
        steps = simuface.simuAccuracy()

    # 过载的父类方法， 初始化车辆，在车辆启动上路时被TESS NG调用一次
    def initVehicle(self, vehi):
        # 设置当前车辆及其驾驶行为过载方法被TESSNG调用频次，即多少个计算周调用一次指定方法。如果对运行效率有极高要求，可以精确控制具体车辆或车辆类型及具体场景相关参数
        self.setStepsPerCall(vehi)
        # 车辆ID，不含首位数，首位数与车辆来源有关，如发车点、公交线路
        tmpId = vehi.id() % 100000
        # 车辆所在路段名或连接段名
        roadName = vehi.roadName()
        # 车辆所在路段ID或连接段ID
        roadId = vehi.roadId()
        return True

    # 重新计算期望速度 单位：m/s
    # vehi：车辆
    # ref_esirSpeed：返回结果,ref_desirSpeed.value是TESS NG计算好的期望速度，可以在此方法改变它
    # return结果：False：TESS NG忽略此方法作的修改，True：TESS NG采用此方法所作修改
    def ref_reCalcdesirSpeed(self, vehi, ref_desirSpeed):
        vId = vehi.id()
        if vId in self.set_speed_list.keys():
            ref_desirSpeed.value = m2p(round(self.set_speed_list[vId] / 3.6, 1))
            # if vId in self.test.keys() and (self.batchNum % 10 == 0):
            #     print(f'时间：{time.time()}，tess车辆id：{vId}，设置速度为{self.set_speed_list[vId]}')
            return True
        return False

    # 过载的父类方法，计算是否要左自由变道
    # vehi:车辆
    # return结果，True：变道、False：不变道
    def reCalcToLeftFreely(self, vehi):

        return False

    # 过载的父类方法，计算是否要右自由变道
    # vehi:车辆
    # return结果，True：变道、False：不变道
    def reCalcToRightFreely(self, vehi):

        return False

    # 过载的父类方法，TESS NG 在每个计算周期结束后调用此方法，大量用户逻辑在此实现，注意耗时大的计算要尽可能优化，否则影响运行效率
    def afterOneStep(self):
        # try:
        # ===============================全局变量===================================================
        # 首先定义全局变量，用于在不同方法内互相通信
        global car_real_type, send_data
        # ===============================车辆顶层接口===============================================
        # TESSNG 顶层接口
        iface = tessngIFace()
        # TESSNG 仿真子接口
        simu_iface = iface.simuInterface()
        # TESSNG 路网子接口
        net_iface = iface.netInterface()
        dispatch_points = net_iface.dispatchPoints()
        batch_num = simu_iface.batchNumber()
        # 当前仿真计算批次
        self.batchNum = batch_num
        # 获取当前时间
        now = datetime.now()
        # 格式化时间为yyyy-MM-ddTHH:mm:ss.fff格式
        formatted_time = now.strftime("%Y-%m-%dT%H:%M:%S") + f".{now.microsecond // 1000:03d}"
        timestamp_milliseconds = int(time.time() * 1000)

        start_time = time.time()

        # ===============================相机感知结果  增加仿真车===============================================
        kako_data = self.kako_data_list.copy()
        for licenseCode in kako_data.keys():
            vInfo = kako_data.get(licenseCode)
            license_code = str(vInfo["licenseCode"])
            longitude = vInfo["lon"]
            latitude = vInfo["lat"]
            laneNum = vInfo["laneNum"]
            type = vInfo["type"]
            speed = vInfo["speed"]
            sn = vInfo["sn"]

            # 删车逻辑处理
            if sn in KAKO_DATA_REMOVE and license_code in self.exists_list_licenseCode_vId.keys():
                # 删除缓存
                vId = self.exists_list_licenseCode_vId.pop(license_code)
                self.exists_list_vId_licenseCode.pop(vId)
                self.set_speed_list.pop(vId)
                self.kako_data_list.pop(license_code)
                vehicle = simu_iface.getVehicle(vId)
                if vehicle:
                    # 删除车辆
                    vehicle.vehicleDriving().stopVehicle()
                    # print(f'删除车辆，TESS车辆ID：{vId}车牌号：{license_code},当前门架：{sn}')
                continue
            # 没有车牌
            if license_code == '默A00000':
                # 发车
                vId = self.send_car(simu_iface, net_iface, longitude, latitude, speed, laneNum, type, f"#ff0000")
                if vId != -1:
                    # print(f'路段车辆创建成功，id：{vId},车牌：{license_code}')
                    # 设置缓存
                    self.exists_list_vId_licenseCode[vId] = license_code
                    self.set_speed_list[vId] = speed
            else:
                # 如果该车辆为第一次出现，则创建对应的背景车
                if license_code not in self.exists_list_licenseCode_vId.keys():
                    # 发车
                    vId = self.send_car(simu_iface, net_iface, longitude, latitude, speed, laneNum, type, f"#0000FF")
                    if vId != -1:
                        # print(f'路段车辆创建成功，id：{vId},车牌：{license_code}')
                        # 设置缓存
                        self.exists_list_licenseCode_vId[license_code] = vId
                        self.exists_list_vId_licenseCode[vId] = license_code
                        self.set_speed_list[vId] = speed
                else:
                    vId = self.exists_list_licenseCode_vId[license_code]
                    vehicle = simu_iface.getVehicle(vId)
                    if vehicle:
                        # 车辆在路网中
                        # 获取仿真车的真实经纬度
                        qPoint = vehicle.pos()
                        x = qPoint.x()
                        y = qPoint.y()
                        sim_lon, sim_lat = self.p(x, -y, inverse=True)
                        curr_speed = round(vehicle.currSpeed() * 3.6, 1)
                        # 判断两个点之间的距离，如果距离小于xx m,则仿真车无需过渡
                        diff_distance = haversine_distance(sim_lon, sim_lat, longitude, latitude)
                        if self.min_move_distance < diff_distance:
                            # 判断在门架前方或后方，进行加速或减速
                            run_time = 1
                            if sn in KAKO_DATA_S:
                                if sim_lon > longitude:
                                    run_time, new_speed = self.add_speed(curr_speed, diff_distance, speed, vId)
                                    if diff_distance >500:
                                        print(
                                        f'当前时间：{time.time()}，车牌号：{license_code}，原始速度：{curr_speed}，加速为：{new_speed}，运行时间：{run_time}，与【{sn}】的距离为{diff_distance}米，')
                                    # self.test[vId] = new_speed
                                else:
                                    run_time, new_speed = self.reduce_speed(curr_speed, diff_distance, speed, vId)
                                    if diff_distance > 500:
                                        print(
                                        f'当前时间：{time.time()}，车牌号：{license_code}，原始速度：{curr_speed}，减速为：{new_speed}，运行时间：{run_time}，与【{sn}】的距离为{diff_distance}米，')
                                    # self.test[vId] = new_speed
                            elif sn in KAKO_DATA_X:
                                if sim_lon < longitude:
                                    print(f'时间：{time.time()}，tess车辆id：{vId}，加速，与{sn}的距离为{diff_distance}')
                                    run_time = self.add_speed(curr_speed, diff_distance, speed, vId)
                                else:
                                    print(f'时间：{time.time()}，tess车辆id：{vId}，减速，与{sn}的距离为{diff_distance}')
                                    run_time = self.reduce_speed(curr_speed, diff_distance, speed, vId)
                            # 创建一个定时器，在3秒后移除该键值对
                            timer = threading.Timer(run_time, self.remove_speed_entry, args=[vId, speed])
                            timer.start()
                    else:
                        # 车辆不在路网中，自己下去了，但是缓存中没有删除，需要重新发车，并更新缓存
                        # 先删除缓存
                        vId = self.exists_list_licenseCode_vId.pop(license_code)
                        self.exists_list_vId_licenseCode.pop(vId)
                        self.set_speed_list.pop(vId)
                        # 发车
                        vId = self.send_car(simu_iface, net_iface, longitude, latitude, speed, laneNum, type,
                                            f"#0000FF")
                        if vId != -1:
                            # print(f'路段车辆创建成功，id：{vId},车牌：{license_code}')
                            # 设置缓存
                            self.exists_list_licenseCode_vId[license_code] = vId
                            self.exists_list_vId_licenseCode[vId] = license_code
                            self.set_speed_list[vId] = speed
            # 删除缓存
            self.kako_data_list.pop(license_code)

        # ================================车辆全域数据==============================================
        all_vehicle_start = simu_iface.allVehiStarted()
        objs = []
        for vehicle in all_vehicle_start:
            # 使用 strftime 方法来格式化时间
            cId = vehicle.id()
            vehicle_driving = vehicle.vehicleDriving()
            speed = round(vehicle.currSpeed() * 3.6, 1)
            angle = vehicle.angle()
            roadId = vehicle.roadId()
            qPoint = vehicle.pos()
            cType = vehicle.vehicleTypeCode()
            x = qPoint.x()
            y = qPoint.y()
            lon, lat = self.p(x, -y, inverse=True)
            if vehicle.roadIsLink():
                ILane_1 = vehicle.lane()
            else:
                ILane_1 = vehicle.toLane()
            ILink = ILane_1.link()
            laneCount = ILink.laneCount()
            laneNum = ILane_1.number()  # 从右往左 依次为 0,1,2
            licenseCode = self.exists_list_vId_licenseCode[cId]
            veh = {}
            veh['id'] = cId
            veh['type'] = cType
            veh['angle'] = round(angle, 2)
            veh['speed'] = speed
            veh['lon'] = round(lon, 6)
            veh['lat'] = round(lat, 6)
            veh['licenseCode'] = licenseCode
            objs.append(veh)
            # if cId in self.test.keys() and (self.batchNum % 10 == 0):
            #     print(f'时间：{time.time()}，tess车辆id：{cId}，速度为{speed}')
        all_veh = {}
        all_veh['timestamp'] = timestamp_milliseconds
        all_veh['count'] = len(objs)
        all_veh['objs'] = objs
        # 通过websocket发送
        try:
            future = asyncio.run_coroutine_threadsafe(self.ws_server.broadcast(str(all_veh).replace("'", '"')),
                                                      self.ws_server.loop)
            future.result()  # 等待协程完成
        except Exception as e:
            print(str(e))

    # 计算减速时间
    def reduce_speed(self, curr_speed, diff_distance, speed, vId):
        new_speed = curr_speed
        if curr_speed > speed:
            new_speed = speed
        new_speed = round(new_speed * 0.7, 1)
        run_time = diff_distance / round(new_speed / 3.6, 2)
        self.set_speed_list[vId] = new_speed
        return run_time + 3, new_speed

    # 计算加速时间
    def add_speed(self, curr_speed, diff_distance, speed, vId):
        new_speed = curr_speed
        if curr_speed < speed:
            new_speed = speed
        new_speed = round(new_speed * 1.3, 1)
        if DEF_SPEED_LIMIT_CONFIG < new_speed:
            new_speed = DEF_SPEED_LIMIT_CONFIG
        run_time = diff_distance / round(new_speed / 3.6, 2)
        self.set_speed_list[vId] = new_speed
        return run_time + 3, new_speed

    # 移除控制加减速的字典
    def remove_speed_entry(self, vId, speed):
        # old_speed = self.set_speed_list.pop(vId)
        self.set_speed_list[vId] = speed
        # self.test[vId] = speed
        # print(f'时间：{time.time()}，在self.set_speed_list中重新设置tess车辆id：{vId}，原始速度: {old_speed}，新速度: {speed}"')

    # 移动车辆到目标点
    def move_car(self, simu_iface, net_iface, vId, longitude, latitude):
        # 移动车辆到该门架
        vehicle = simu_iface.getVehicle(vId)
        vehicle_driving = vehicle.vehicleDriving()
        lon, lat = self.p(longitude, latitude, inverse=False)
        q_point = QPointF()
        q_point.setX(lon)
        q_point.setY(-lat)
        # 获取离目标点最近的坐标，共9个单元格，一般 lLocation是最近的
        l_location = net_iface.locateOnCrid(q_point, 9)
        if len(l_location) > 0:
            vehicle_driving.move(l_location[0].pLaneObject, l_location[0].distToStart)

    # 发车
    def send_car(self, simu_iface, net_iface, longitude, latitude, speed, laneNumber, carType, color):
        # 如果该车辆为第一次出现，则创建对应的背景车
        lon, lat = self.p(longitude, latitude, inverse=False)
        q_point = QPointF()
        q_point.setX(lon)
        q_point.setY(-lat)
        # 获取离目标点最近的坐标，共9个单元格，一般 lLocation是最近的
        l_location = net_iface.locateOnCrid(q_point, 9)
        # 判断获取到最近的坐标集合是否为空
        if len(l_location) > 0:
            p_lane_object = l_location[0].pLaneObject
            # 路段发车
            if isinstance(p_lane_object, ILane):
                # 发车信息初始化
                dvp = Online.DynaVehiParam()
                dvp.vehiTypeCode = car_real_type.get(carType, car_real_type_default_value)
                dvp.laneNumber = p_lane_object.number()
                p_link = p_lane_object.link()
                p_link_id = p_link.id()
                dvp.roadId = p_link_id
                dvp.dist = l_location[0].distToStart
                dvp.speed = round(speed / 3.6, 1)
                dvp.color = color
                vehicle = simu_iface.createGVehicle(dvp, True)
                if vehicle is not None:
                    vehicle.initSpeed(round(speed / 3.6, 1))  # 单位：m/s
                    return vehicle.id()
                else:
                    print('路段车辆创建失败')
                    return -1
            # 连接段发车
            else:
                # 发车信息初始化
                dvp = Online.DynaVehiParam()
                dvp.vehiTypeCode = car_real_type.get(carType, car_real_type_default_value)
                # 获取连接段
                connector = p_lane_object.connector()
                # 根据车辆类型在指定车道发车（机动车仅能在机动车道行驶，非机动车(5,6,7)则能在所有车道行驶） 目前能够精确到具体车道
                from_link = connector.fromLink()
                dvp.laneNumber = random.randint(1, 2)
                # 这里理论上需要计算发车点距离路段起点的位置
                # （会首先获取当前车道所有的起点位置，计算起点位置距离车辆经纬度的距离，作为发车点距离路段起点的距离）
                dvp.roadId = from_link.id()
                dvp.dist = from_link.length() - 1
                dvp.speed = round(speed / 3.6, 1)
                dvp.color = f"#FFFFFF"
                vehicle = simu_iface.createGVehicle(dvp, True)
                if vehicle is not None:
                    vehicle.initSpeed(round(speed / 3.6, 1))  # 单位：m/s
                    return vehicle.id()
                else:
                    print('路段车辆创建失败')
                    return -1
        else:
            print("车辆不在路网上")
            return -1


def get_values_based_on_count(data_dict, threshold):
    # 统计每个 value 的出现次数
    value_counts = Counter(data_dict.values())
    # 获取所有大于阈值的 value
    result_values = [value for value, count in value_counts.items() if count > threshold]
    return result_values


def find_key_by_value(dictionary, value):
    for key, val in dictionary.items():
        if val == value:
            return key
    return None


# 检查目标pileNumber是否在给定的列表中
def find_pile_in_list(pile_number, data_list):
    for index, item in data_list:
        if item['pileNumber'] == pile_number:
            return index
    return -1
